﻿using Couchbase;
using Enyim.Caching.Memcached;
using Enyim.Caching.Memcached.Results;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GuoZhiQi.MiddleCache.Object.Atomic
{
    public abstract class CouchBaseAtomicString
         : CouchBaseAtomic<string>, ICouchBaseObject
    {
        #region Main value.

        /// <summary>
        /// Value of this parameter.
        /// </summary>
        public override string Value
        {
            get;
            set;
        }

        #endregion Main value.

        #region Constructor

        /// <summary>
        /// Constructor of this object.
        /// </summary>
        /// <param name="client">Membase client to work with.</param>
        public CouchBaseAtomicString(CouchbaseClient client)
            : base(client)
        {
        }

        /// <summary>
        /// Constructor of this object.
        /// </summary>
        /// <param name="client">Membase client to work with.</param>
        /// <param name="objectKey">String key of the object.</param>
        /// <param name="TryLoadFromDB">True if object should be instantly loaded from Membase.</param>
        public CouchBaseAtomicString(CouchbaseClient client, string objectKey, bool TryLoadFromDB = false)
            : base(client, objectKey, TryLoadFromDB)
        {
            this.Value = null;
        }

        #endregion Constructor

        #region Atomic functions.

        /// <summary>
        /// Appends string to the parameter and refreshes with new value. Atomic function.
        /// </summary>
        /// <param name="client">Client.</param>
        /// <param name="addedString">String to be appended.</param>
        public void Append(string addedString)
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            if (!this._client.Append(this.Key, new ArraySegment<byte>(Encoding.UTF8.GetBytes(addedString))))
                throw new NullReferenceException("Key not found in database.");

            if (this._expiresAfter.HasValue)
                this._client.Touch(this.Key, this._expiresAfter.Value);
            else if (this._expiresOn.HasValue)
                this._client.Touch(this.Key, this._expiresOn.Value);

            this.Value = (string)this._client.Get(this.Key);
            this.JsonInSync = true;
        }

        /// <summary>
        /// Prepends string to the parameter and refreshes with new value. Atomic function.
        /// </summary>
        /// <param name="client">Client.</param>
        /// <param name="addedString">String to be prepended.</param>
        public void Prepend(string addedString)
        {
            if (this.Key == null)
                throw new NullReferenceException("Key is not set yet.");

            if (!this._client.Prepend(this.Key, new ArraySegment<byte>(Encoding.UTF8.GetBytes(addedString))))
                throw new NullReferenceException("Key not found in database.");

            if (this._expiresAfter.HasValue)
                this._client.Touch(this.Key, this._expiresAfter.Value);
            else if (this._expiresOn.HasValue)
                this._client.Touch(this.Key, this._expiresOn.Value);

            this.Value = (string)this._client.Get(this.Key);
            this.JsonInSync = true;
        }

        #endregion Atomic functions.

        #region Membase object overrides.

        /// <summary>
        /// Adds object to Membase if it does not exist.
        /// </summary>
        /// <exception cref="MembaseException">Throws MembaseException on storage error.</exception>
        /// <returns>Returns false if object already exists.</returns>
        public override bool Add()
        {
            IStoreOperationResult result = this.ExecuteStoreMethod(StoreMode.Add, false, this.Value);
            if (result.Success)
                this.SaveMembaseParams();
            this.JsonInSync = result.Success;
            return result.Success;
        }

        /// <summary>
        /// Sets or adds object.
        /// </summary>
        /// <returns>Returns false if failed.</returns>
        public override bool Set()
        {
            if (this.Key == null)
                throw new ArgumentNullException("Key for Membase item not set yet.", (Exception)null);

            IStoreOperationResult result = this.ExecuteStoreMethod(StoreMode.Set, false, this.Value);
            if (result.Success)
                this.SaveMembaseParams();
            this.JsonInSync = result.Success;
            return result.Success;
        }

        /// <summary>
        /// Sets or adds object only if it has not changed since last refresh.
        /// </summary>
        /// <returns>Returns false if failed.</returns>
        public override bool SafeSet()
        {
            IStoreOperationResult result = this.ExecuteStoreMethod(StoreMode.Set, true, this.Value);
            if (result.Success)
                this.SaveMembaseParams();
            this.JsonInSync = result.Success;
            return result.Success;
        }

        /// <summary>
        /// Replaces value of existing object in Membase.
        /// </summary>
        /// <returns>Returns false if object does not exist.</returns>
        public override bool Replace()
        {
            IStoreOperationResult result = this.ExecuteStoreMethod(StoreMode.Replace, false, this.Value);
            if (result.Success)
                this.SaveMembaseParams();
            this.JsonInSync = result.Success;
            return result.Success;
        }

        /// <summary>
        /// Reloads object with data.
        /// </summary>
        /// <param name="withTouch">If set to true, expiery date/timespan is refreshed on items. Make sure to set the expiery date/timespan before.</param>
        /// <exception cref="MembaseException">Throws MembaseException on storage error.</exception>
        /// <returns>Returns false if reload failed.</returns>
        public override bool Refresh(bool withTouch = false)
        {
            if (this.Key == null)
                throw new ArgumentNullException("Key for Membase item not set yet.", (Exception)null);

            this.InvalidateJsonValue();
            IGetOperationResult result = this._client.ExecuteGet(this.Key);

            if (result.Success)
            {
                this.CAS = result.Cas;
                this.Value = result.Value.ToString();
                this.RefreshMembaseParams();

                if (withTouch)
                {
                    if (this._expiresAfter.HasValue)
                        this._client.Touch(this.Key, this._expiresAfter.Value);
                    else if (this._expiresOn.HasValue)
                        this._client.Touch(this.Key, this._expiresOn.Value);
                }
            }
            else if (result.Exception != null)
                throw new CouchBaseException(this.Key, "Error occured in membase.", result.Exception);

            this.JsonInSync = result.Success;
            return result.Success;
        }

        #endregion Membase object overrides.
    }
}
